The quarter ends where the reader is, not seven hours earlier - #30
Open
nguyenngothuong wants to merge 1 commit into
Open
The quarter ends where the reader is, not seven hours earlier#30nguyenngothuong wants to merge 1 commit into
nguyenngothuong wants to merge 1 commit into
Conversation
`useConsole` built its horizon boundaries with
`new Date(y, m, 1).toISOString().slice(0, 10)`. That constructs local midnight and then
prints it in UTC, so at any positive offset the string comes back a day early: at UTC+07
the third quarter of 2026 ended on 2026-09-30 rather than 2026-10-01.
The boundaries are compared against `close_date`, which the server sends as a plain
calendar date with no zone on it, so an opportunity closing on the last day of the
quarter satisfied `close >= quarterEnd` and was filtered out. `This quarter` is the
default horizon on the sales console, which is the application's landing page — so every
reader in Asia, Australia or eastern Europe opened the CRM to an empty pipeline while a
reader in London opened the same tenant and saw it full. `today` had the same fault: for
the first seven hours of a local day it named the previous one.
Measured against the running sample, same tenant, same rows, browser timezone the only
variable:
origin/dev TZ=Asia/Bangkok open pipeline = $0
origin/dev TZ=UTC open pipeline = $184k
this commit TZ=Asia/Bangkok open pipeline = $184k
The boundaries move into an exported `horizonBounds(now)` so they can be pinned at a date
*and* a timezone; `localDay` formats a date's local calendar day and is the only thing
either of them now goes through. `src/features/sales/__tests__/consoleHorizon.test.ts`
runs under `TZ=Asia/Bangkok` — at UTC every one of its assertions passes against the
unfixed code, which is why the suite never caught this. Four of its five fail against the
previous implementation.
This is the same root cause as the `liveRecords` day-formatting assertion that fails on
this commit's parent east of UTC; that one is left alone, because it is a bug in the
expectation and this is a bug in the application.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: nguyenngothuong <nguyenthuongtb12@gmail.com>
This was referenced Aug 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The sales console is the application’s landing page, and east of Greenwich it opens empty.
What happens
useConsolebuilds its horizon boundaries withnew Date(y, m, 1).toISOString().slice(0, 10). That constructs local midnight and then prints it in UTC, so at any positive offset the string comes back a day early — at UTC+07 the third quarter of 2026 ends on2026-09-30instead of2026-10-01.Those boundaries are compared against
close_date, which the server sends as a plain calendar date with no zone on it. An opportunity closing on the last day of the quarter therefore satisfiesclose >= quarterEndand is filtered out.This quarteris the default horizon on the console, so the effect is not subtle:devTZ=UTCdevTZ=Asia/BangkokTZ=Asia/BangkokSame tenant, same rows, same seed — browser timezone the only variable. Measured against
docker compose up, driven with Playwright.todayhad the same fault by the same route: for the first seven hours of a local day it named the previous one, which is whatclosingThisMonthfilters from.The change
localDayformats a date’s local calendar day, and it is the only thing the boundaries now go through. They move into an exportedhorizonBounds(now)so they can be pinned at a date and a timezone —useConsolepasses the real clock.Tests
src/features/sales/__tests__/consoleHorizon.test.ts, written first, runs underTZ=Asia/Bangkok. Four of its five assertions fail against the previous implementation; all five pass against this one. It sets the zone deliberately: at UTC every one of them passes against the unfixed code, which is why the suite never caught this.Not in this change
liveRecords.test.ts > renders a server timestamp as a dayfails ondeveast of UTC and still fails here. It is the same root cause, but it is a bug in the expectation — the test asserts28 Aug 2026for aT23:18:06Ztimestamp — and this one is a bug in the application. Happy to fix it in a second PR; it seemed wrong to bundle a test correction with a behaviour fix.npm run lintcould not be run at all: the repository has noeslint.config.jsand ESLint 9 requires one. Filing that separately.Signed off under the DCO. Verified:
tsc -bclean,npm run buildclean,npm test136 passed with the one pre-existing failure above.